/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_demo.h"
#include <stddef.h>
#include "ola_pal_os.h"
#include "ola_sdk_api.h"
#include "securec.h"
#include "cJSON.h"

#ifdef SUPPORT_SPEKE_3
static const char *g_spekePincode = "12345678";
#else
static const char *g_spekePincode = "Rk1QK$yD";
#endif
static const char *g_pecdhPincode = "keyborad";
static const char *g_uniqueId = "1234567890";

bool g_switchStatus = false;

/* 本地加密存储涉及的派生密钥素材 */
static const unsigned char g_keyMaterial[] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};

#ifdef OLA_DEMO_TEST
/* 打桩设备属性数据模型 */
static const char *g_devProperties =
    "{"
        "\"services\":["
            "{"
                "\"siid\":\"4\","
                "\"properties\":["
                    "{"
                        "\"iid\":\"1\","
                        "\"value\":\"0\""
                    "},"
                    "{"
                        "\"iid\":\"5\","
                        "\"value\":["
                            "{"
                                "\"index\":\"1\","
                                "\"indexValue\":\"222\""
                            "},"
                            "{"
                                "\"index\":\"2\","
                                "\"indexValue\":\"223\""
                            "},"
                            "{"
                                "\"index\":\"3\","
                                "\"indexValue\":\"224\""
                            "}"
                        "]"
                    "}"
                "]"
            "}"
        "]"
    "}";
#endif

/* 打桩数据模型 */
static const char *g_devModelStr =
    "{"
        "\"device\":{"
            "\"title\":\"AirConditioner\","
            "\"discription\":\"Air Conditioner Overall Service\","
            "\"productId\":\"000004\","
            "\"services\":["
                "{"
                    "\"sname\":\"ac\","
                    "\"suuid\":\"100117\","
                    "\"siid\":\"1\","
                    "\"dmProperty\":["
                        "{"
                            "\"pname\":\"on\","
                            "\"puuid\":\"201048\","
                            "\"iid\":\"1\","
                            "\"type\":\"bool\","
                            "\"discription\":\"Power On/Off Status\","
                            "\"readable\":\"true\","
                            "\"writable\":\"true\""
                        "},"
                        "{"
                            "\"pname\":\"acMode\","
                            "\"puuid\":\"201066\","
                            "\"iid\":\"2\","
                            "\"type\":\"uint8\","
                            "\"discription\":\"Air Purifier Working Mode\","
                            "\"readable\":\"true\","
                            "\"writable\":\"true\""
                        "},"
                        "{"
                            "\"pname\":\"targetTemperature\","
                            "\"puuid\":\"201044\","
                            "\"iid\":\"3\","
                            "\"type\":\"float\","
                            "\"discription\":\"Target Temperature\","
                            "\"readable\":\"true\","
                            "\"writable\":\"true\""
                        "}"
                    "]"
                "}"
            "]"
        "}"
    "}";

/* 打桩设备私钥 */
static const char *g_privatekey = "-----BEGIN PRIVATE KEY-----\r\n"
    "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg2qDxEiog1FwRIvo1\r\n"
    "PYjcPMURWmeTFBvQhpFDJQthYLSgCgYIKoZIzj0DAQehRANCAAS47FQETencUM9o\r\n"
    "28RgBg6Shp+jvpYoPwiBJRk8E80LIJ4bk/Tua+PwPZYOmR2MwLbHrt0bHJx3wX0A\r\n"
    "6DhXjQYu\r\n"
    "-----END PRIVATE KEY-----\r\n"
    "";

/* 打桩设备证书 */
static const char *g_devCertificate = "MIIDCDCCAo2gAwIBAgISICEHGRZHECC+BYZkKWCZyIYSMAoGCCqGSM49BAMDMF0x"
    "CzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZIdWF3ZWkxEzARBgNVBAsMCkh1YXdlaSBD"
    "QkcxKDAmBgNVBAMMH0h1YXdlaSBDQkcgRXF1aXBtZW50IFMxIENBIFRlc3QwHhcN"
    "MjEwNzE5MDg0NzEwWhcNMzEwNzE5MDg0NzEwWjBOMRkwFwYDVQQDDBBPTEEtVWF0"
    "RXF1UzFUZXN0MRMwEQYDVQQLDApIdWF3ZWkgQ0JHMQ8wDQYDVQQKDAZIdWF3ZWkx"
    "CzAJBgNVBAYTAkNOMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOxUBE3p3FDP"
    "aNvEYAYOkoafo76WKD8IgSUZPBPNCyCeG5P07mvj8D2WDpkdjMC2x67dGxycd8F9"
    "AOg4V40GLqOCATowggE2MB8GA1UdIwQYMBaAFOWTzykFL2dTqqhniJBsfOlGEBMM"
    "MB0GA1UdDgQWBBTmAG648X3jKaDr7pS+PrJNMbuOTzBGBgNVHSAEPzA9MDsGBFUd"
    "IAAwMzAxBggrBgEFBQcCARYlaHR0cDovL3BraS5jb25zdW1lci5odWF3ZWkuY29t"
    "L2NhL2NwczASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwICtDBJBgNV"
    "HR8EQjBAMD6gPKA6hjhodHRwOi8vcGtpLmNvbnN1bWVyLmh1YXdlaS5jb20vY2Ev"
    "Y3JsL2VxdV9zMS9jcmwyMDIxLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB"
    "BQUHAwIwHgYMKwYBBAGPWwKCeAEBBA4TDE1BWUNBUUVLQVFFPTAKBggqhkjOPQQD"
    "AwNpADBmAjEA9fEZ+cSQ45BkwuqAizNhlkD/THLus5LMAjg/hgCM1+1J4JGr/P6c"
    "OR9RmMmoK3guAjEA5oKXdV9KO8IGh6o2tdXATx3GPOFntawEVjDq5f9+vLEmxwzA"
    "piN1wSRoP83nO+q1"
    "";

/* 打桩CA中间证书 */
static const char *g_capem = "MIIF1DCCBVqgAwIBAgIEEAAADzAKBggqhkjOPQQDAzA1MQswCQYDVQQGEwJDTjEM"
    "MAoGA1UECgwDT0xBMRgwFgYDVQQDDA9PTEEgRUNDIFJPT1QgQ0EwHhcNMjEwNDA5"
    "MDk0MTU3WhcNNDEwNDA0MDk0MTU3WjBdMQswCQYDVQQGEwJDTjEPMA0GA1UECgwG"
    "SHVhd2VpMRMwEQYDVQQLDApIdWF3ZWkgQ0JHMSgwJgYDVQQDDB9IdWF3ZWkgQ0JH"
    "IEVxdWlwbWVudCBTMSBDQSBUZXN0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEPOfL"
    "ryxsu3lSdX4ShqmIN55ZCTfYlaITucFG7M/zJJgCbjoYsexMxCkhk/qZxQzT2PCs"
    "AYSo4d2nELCrpFpc2kaM74Nroj4CiZnNAZ/y28NnrvqSaFvlSye6gkgdozeTo4IE"
    "ETCCBA0wHQYDVR0OBBYEFOWTzykFL2dTqqhniJBsfOlGEBMMMIIBDgYDVR0fBIIB"
    "BTCCAQEwbKBqoGiGZmxkYXA6Ly9wa2kub2xhLWlvdC5jb206MzkxL2NuPWFybDAs"
    "c3Q9MixvPU9MQSBFQ0MgUk9PVCBDQUFSTCxPPU9MQSxDPUNOP2F1dGhvcml0eVJl"
    "dm9jYXRpb25MaXN0O2JpbmFyeTBboFmgV6RVMFMxCzAJBgNVBAYTAkNOMQwwCgYD"
    "VQQKDANPTEExGzAZBgNVBAoMEk9MQSBFQ0MgUk9PVCBDQUFSTDEKMAgGA1UECAwB"
    "MjENMAsGA1UEAwwEYXJsMDA0oDKgMIYuaHR0cDovL3BraS5vbGEtaW90LmNvbTox"
    "MDAwNC9lY2NmaWxlLzJhcmwwLmNybDBgBgNVHSMEWTBXgBTHlelr1uJVGfbt/HAD"
    "YLxTpVe14KE5pDcwNTELMAkGA1UEBhMCQ04xDDAKBgNVBAoMA09MQTEYMBYGA1UE"
    "AwwPT0xBIEVDQyBST09UIENBggQQAAANMBIGA1UdEwEB/wQIMAYBAf8CAQIwDgYD"
    "VR0PAQH/BAQDAgEGMIH7BggrBgEFBQcBAQSB7jCB6zBZBggrBgEFBQcwAoZNbGRh"
    "cDovL3BraS5vbGEtaW90LmNvbTozOTEvQ049T0xBIEVDQyBST09UIENBLE89T0xB"
    "LEM9Q04/Y0FDZXJ0aWZpY2F0ZTtiaW5hcnkwQwYIKwYBBQUHMAKkNzA1MQswCQYD"
    "VQQGEwJDTjEMMAoGA1UECgwDT0xBMRgwFgYDVQQDDA9PTEEgRUNDIFJPT1QgQ0Ew"
    "SQYIKwYBBQUHMAKGPWh0dHA6Ly9wa2kub2xhLWlvdC5jb206MTAwMDQvZWNjZmls"
    "ZS9PTEFFQ0NST09UQ0ExMDAwMDAwZC5jZXIwggFUBggrBgEFBQcBCwSCAUYwggFC"
    "MHoGCCsGAQUFBzAFhm5sZGFwOi8vcGtpLm9sYS1pb3QuY29tOjM5MS9DTj1IdWF3"
    "ZWkgQ0JHIEVxdWlwbWVudCBTMSBDQSBUZXN0LE9VPUh1YXdlaSBDQkcsTz1IdWF3"
    "ZWksQz1DTj9jQUNlcnRpZmljYXRlO2JpbmFyeTBrBggrBgEFBQcwBaRfMF0xCzAJ"
    "BgNVBAYTAkNOMQ8wDQYDVQQKDAZIdWF3ZWkxEzARBgNVBAsMCkh1YXdlaSBDQkcx"
    "KDAmBgNVBAMMH0h1YXdlaSBDQkcgRXF1aXBtZW50IFMxIENBIFRlc3QwVwYIKwYB"
    "BQUHMAWGS2h0dHA6Ly9wa2kub2xhLWlvdC5jb206MTAwMDQvZWNjZmlsZS9IdWF3"
    "ZWlDQkdFcXVpcG1lbnRTMUNBVGVzdDEwMDAwMDBmLmNlcjAKBggqhkjOPQQDAwNo"
    "ADBlAjEA9Sxu6IrgkxWXT2zh3Py11dbPcrPnQ+7j/7z1p33+OGyDBCbTfGKXYiEr"
    "MPGcdJ4HAjAsmvSpSAYLu1o7FH56DYE/1uZvRyK7yjPL22qReGrwodKzxP2MN8aU"
    "PLNwo9cJy1w="
    "";
static const OlaLifeCycleManger *g_olaHandler = NULL;

static OlaInitParam g_initPara = {
    .taskPara = {
        .taskPriority = 26,
        .taskSize = 0x1000, // PECDH协商时，此处需要改为0x2000，否则出现栈溢出
    },
    .devPara = {
        .sn = "12345678",
        .model = "DEVICEMODEL",
        .catID = "01D1",
        .vendorID = "0001",
        .devName = "Table Lamp",
        .prodId = "2DDZ",
        .mac = "DEVICEMAC",
        .fwv = "1.0.0",
        .hwv = "1.0.0",
        .swv = "1.0.0",
        .manuName = "HUAW",
        .platformId = "HW",
        .type = OLA_PROTOCOL_TYPE_WIFI,
    },
    .netcfgPara = {
        .negoType = OLA_SEC_NEGO_TYPE_SPEKE,
        .codeCap = OLA_DEV_CODE_CAP_PIN_CODE,
        .pinCodeType = OLA_PINCODE_TYPE_OUTSIDE,
        .protocol = OLA_NETCFG_PROTOCOL_TYPE_SOFTAP,
        .authType = OLA_DEV_AUTH_TYPE_CERT,
        .timeout = 600,
        .customSsidData = "GGGGGGGG",
    },
    .runingPara = {
        .logLevel = OLA_LOG_LEVEL_DEBUG,
        .coapRecvBufSize = 0x1000,
        .coapSendBufSize = 0x1000,
    }
};

static int GetDeviceModel(OlaData *out)
{
    out->data = OlaMalloc(strlen(g_devModelStr) + 1);
    if (out->data == NULL) {
        return -1;
    }
    (void)memset_s(out->data, strlen(g_devModelStr) + 1, 0, strlen(g_devModelStr) + 1);
    if (strcpy_s(out->data, strlen(g_devModelStr) + 1, g_devModelStr) != EOK) {
        OlaFree(out->data);
        out->data = NULL;
        return -1;
    }
    out->len = strlen(g_devModelStr);
    return 0;
}

static int GetNegoPincode(OlaData *out)
{
    const char *pincode = (g_initPara.netcfgPara.negoType == OLA_SEC_NEGO_TYPE_SPEKE) ?
        g_spekePincode : g_pecdhPincode;
    out->data = OlaMalloc(strlen(pincode) + 1);
    if (out->data == NULL) {
        return -1;
    }
    (void)memset_s(out->data, strlen(pincode) + 1, 0, strlen(pincode) + 1);
    if (strcpy_s(out->data, strlen(pincode) + 1, pincode) != EOK) {
        OlaFree(out->data);
        out->data = NULL;
        return -1;
    }
    out->len = strlen(pincode);
    return 0;
}

static int GetAuthDevicePrivateKey(OlaData *out)
{
    out->data = OlaMalloc(strlen(g_privatekey) + 1);
    if (out->data == NULL) {
        return -1;
    }
    (void)memset_s(out->data, strlen(g_privatekey) + 1, 0, strlen(g_privatekey) + 1);
    if (strcpy_s(out->data, strlen(g_privatekey) + 1, g_privatekey) != EOK) {
        OlaFree(out->data);
        out->data = NULL;
        return -1;
    }
    out->len = strlen(g_privatekey);
    return 0;
}

static int GetAuthDeviceCertChain(OlaData *out)
{
    const char *connector = ";";
    out->len = strlen(g_devCertificate) + strlen(connector) + strlen(g_capem);
    out->data = OlaMalloc(out->len + 1);
    if (out->data == NULL) {
        return -1;
    }
    (void)memset_s(out->data, out->len + 1, 0, out->len + 1);

    int len = sprintf_s(out->data, out->len + 1, "%s%s%s", g_devCertificate, connector, g_capem);
    if (len <= 0) {
        return -1;
    }

    return 0;
}

static int GetStorageKeyMaterial(OlaData *out)
{
    out->data = OlaMalloc(sizeof(g_keyMaterial) + 1);
    if (out->data == NULL) {
        return -1;
    }
    (void)memset_s(out->data, sizeof(g_keyMaterial) + 1, 0, sizeof(g_keyMaterial) + 1);
    if (memcpy_s(out->data, sizeof(g_keyMaterial) + 1, g_keyMaterial, sizeof(g_keyMaterial)) != EOK) {
        OlaFree(out->data);
        out->data = NULL;
        return -1;
    }
    out->len = sizeof(g_keyMaterial);
    return 0;
}

static int GetUniqueId(OlaData *out)
{
    out->data = OlaMalloc(strlen(g_uniqueId) + 1);
    if (out->data == NULL) {
        return -1;
    }
    (void)memset_s(out->data, strlen(g_uniqueId) + 1, 0, strlen(g_uniqueId) + 1);
    if (strcpy_s(out->data, strlen(g_uniqueId) + 1, g_uniqueId) != EOK) {
        OlaFree(out->data);
        out->data = NULL;
        return -1;
    }
    out->len = strlen(g_uniqueId);
    return 0;
}

static int GetDeviceData(OlaDeviceDataType type, OlaData *out)
{
    if (out == NULL) {
        return -1;
    }
    switch (type) {
        case OLA_DATA_TYPE_JSON_DEVICE_MODEL:
            return GetDeviceModel(out);
        case OLA_DATA_TYPE_NETCFG_PINCODE:
            return GetNegoPincode(out);
        case OLA_DATA_TYPE_AUTH_DEVICE_PRIVATE_KEY:
            return GetAuthDevicePrivateKey(out);
        case OLA_DATA_TYPE_AUTH_DEVICE_CERT_CHAIN:
            return GetAuthDeviceCertChain(out);
        case OLA_DATA_TYPE_STORAGE_KEY_MATERIAL:
            return GetStorageKeyMaterial(out);
        case OLA_DATA_TYPE_UNIQUE_ID:
            return GetUniqueId(out);
        default:
            break;
    }

    return 0;
}

static void ChangeSwitchStatus(const cJSON *item)
{
    if (item == NULL) {
        printf("ChangeSwitchStatus, invalid item");
        return;
    }
    cJSON *properties = cJSON_GetObjectItem(item, "properties");
    if (properties == NULL) {
        return;
    }
    int arrSize = cJSON_GetArraySize(properties);
    printf("ChangeSwitchStatus, properties size: %d\r\n", arrSize);
    for (int i = 0; i < arrSize; i++) {
        cJSON *pItem = cJSON_GetArrayItem(properties, i);
        if (pItem == NULL) {
            printf("ChangeSwitchStatus, pItem null\r\n");
            return;
        }
        cJSON *iid = cJSON_GetObjectItem(pItem, "iid");
        if ((iid == NULL) || (!cJSON_IsString(iid)) || (iid->valuestring == NULL)) {
            printf("ChangeSwitchStatus, iid null\r\n");
            return;
        }
        if (iid->valuestring[0] != '1') {
            continue;
        }
        cJSON *value = cJSON_GetObjectItem(pItem, "value");
        if ((value == NULL) || (!cJSON_IsString(value)) || (value->valuestring == NULL)) {
            printf("ChangeSwitchStatus, value null\r\n");
            return;
        }
        if (value->valuestring[0] == '1') {
            g_switchStatus = true;
        } else {
            g_switchStatus = false;
        }
        printf("ChangeSwitchStatus, g_switchStatus: %d\r\n", g_switchStatus);
    }
}

static int CtrlDeviceHandler(const cJSON *item)
{
    if (item == NULL) {
        printf("CtrlDeviceHandler, invalid item");
        return -1;
    }
    cJSON *siid = cJSON_GetObjectItem(item, "siid");
    if ((siid == NULL) || (!cJSON_IsString(siid)) || (siid->valuestring == NULL)) {
        printf("CtrlDeviceHandler, json get siid str err\r\n");
        return -1;
    }
    printf("CtrlDeviceHandler, siid : %s\r\n", siid->valuestring);
    switch (siid->valuestring[0]) {
        case '4': {
            ChangeSwitchStatus(item);
            break;
        }
        default:
            printf("invalid siid, %s\r\n", siid->valuestring);
    }
    return 0;
}

// "{\"services\":[{\"siid\":\"4\",\"properties\":[{\"iid\":\"1\",\"value\":\"1\"}]}]}"
static int CtrlDeviceService(const OlaData *json, OlaReqId id)
{
    (void)id;
    if ((json == NULL) || (json->data == NULL) || (json->len == 0)) {
        printf("CtrlDeviceService, invalid data");
        return -1;
    }
    cJSON *data = cJSON_Parse((char *)json->data);
    if (data == NULL) {
        printf("CtrlDeviceService, parse data failed");
        return -1;
    }
    cJSON *services = cJSON_GetObjectItem(data, "services");
    if (services == NULL) {
        printf("CtrlDeviceService, parse services failed");
        cJSON_Delete(data);
        return -1;
    }
    int size = cJSON_GetArraySize(services);
    printf("CtrlDeviceService, services size: %d\r\n", size);
    for (int i = 0; i < size; i++) {
        cJSON *item = cJSON_GetArrayItem(services, i);
        (void)CtrlDeviceHandler(item);
    }
    cJSON_Delete(data);
    return 0;
}

static int QueryDeviceService(const OlaData *json, OlaData *out)
{
    (void)json;
    if (out == NULL) {
        return -1;
    }
    /* 设备属性查询打桩代码，返回固定数据 */
    const char *properties = "{\"services\":[{\"siid\":\"4\",\"properties\":[{\"iid\":\"1\",\"value\":\"%s\"}]}]}";
    const char *status;
    if (g_switchStatus) {
        status = "1";
    } else {
        status = "0";
    }
    out->len = strlen(properties) + 1;
    out->data = OlaMalloc(strlen(properties) + 1);
    if (out->data == NULL) {
        printf("QueryDeviceService malloc error\r\n");
        return -1;
    }
    (void)memset_s(out->data, strlen(properties) + 1, 0, strlen(properties) + 1);
    if (snprintf_s((char *)out->data, out->len, out->len - 1, properties, status) < 0) {
        OlaFree(out->data);
        out->len = 0;
        printf("QueryDeviceService, snprintf_s\r\n");
        return -1;
    }
    out->len = strlen((char *)out->data);
    return 0;
}

static void NotifySdkEvent(OlaEventCode code)
{
    return;
}

static void DiagnosisLogOutput(unsigned int errcode, const OlaData *msg)
{
    return;
}

static OlaCallback g_callback = {
    .getDeviceData = GetDeviceData,
    .ctrlDeviceService = CtrlDeviceService,
    .queryDeviceService = QueryDeviceService,
    .notifySdkEvent = NotifySdkEvent,
    .diagnosisLogOutput = DiagnosisLogOutput,
};


int OlaDemoMain(void)
{
    g_olaHandler = OlaInit(&g_initPara, &g_callback);
    if ((g_olaHandler == NULL) || (g_olaHandler->start == NULL)) {
        printf("ola init error\r\n");
        return -1;
    }

    return g_olaHandler->start();
}